/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { DiskLruCache, Editor, MutableIterator, Snapshot } from './cache/DiskLruCache'
import { TaskRunner } from './concurrent/TaskRunner'
import { FileSystem, SystemFileSystem } from './io/FileSystem'
import { Response } from './Response'
import { Request } from './Request'
import { HttpUrl } from './HttpUrl'
import { CacheRequest } from './cache/CacheRequest'
import { HttpMethod } from './http/HttpMethod'
import { buffer, TreeSet, util } from '@kit.ArkTS'
import { cryptoFramework } from '@kit.CryptoArchitectureKit'
import { ResponseBody } from './ResponseBody'
import { BufferedSource } from './okio/BufferedSource'
import { MediaType } from './MediaType'
import { ForwardingSource } from './okio/ForwardingSource'
import { Source } from './okio/Source'
import { Handshake } from './Handshake'
import { Headers } from './Headers'
import { Protocol } from './Protocol'
import { FileReader } from './io/FileReader'
import { fileIo as fs } from '@kit.CoreFileKit';
import { EMPTY_HEADERS, kotlinCheck } from './utils/Utils'
import { ArrayIsEquals } from './utils/ArrayUtils'
import { StatusLine } from './http/StatusLine'
import { cert } from '@kit.DeviceCertificateKit'
import { CertificateException, IOException, NoSuchElementException } from './Error/error'
import { CipherSuite } from './CipherSuite'
import { TlsVersion } from './TlsVersion'
import { Sink } from './okio/Sink'
import { ForwardingSink } from './okio/ForwardingSink'
import { CacheStrategy } from './cache/CacheStrategy'

/**
 * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and
 * bandwidth.
 *
 * ## Cache Optimization
 *
 * To measure cache effectiveness, this class tracks three statistics:
 *
 *  * **[Request Count:][requestCount]** the number of HTTP requests issued since this cache was
 *    created.
 *  * **[Network Count:][networkCount]** the number of those requests that required network use.
 *  * **[Hit Count:][hitCount]** the number of those requests whose responses were served by the
 *    cache.
 *
 * Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of
 * the response, the client will issue a conditional `GET`. The server will then send either
 * the updated response if it has changed, or a short 'not modified' response if the client's copy
 * is still valid. Such responses increment both the network count and hit count.
 *
 * The best way to improve the cache hit rate is by configuring the web server to return cacheable
 * responses. Although this client honors all [HTTP/1.1 (RFC 7234)][rfc_7234] cache headers, it
 * doesn't cache partial responses.
 *
 * ## Force a Network Response
 *
 * In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip
 * the cache, and fetch data directly from the server. To force a full refresh, add the `no-cache`
 * directive:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder().noCache().build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * ```
 *
 * If it is only necessary to force a cached response to be validated by the server, use the more
 * efficient `max-age=0` directive instead:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder()
 *         .maxAge(0, TimeUnit.SECONDS)
 *         .build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * ```
 *
 * ## Force a Cache Response
 *
 * Sometimes you'll want to show resources if they are available immediately, but not otherwise.
 * This can be used so your application can show *something* while waiting for the latest data to be
 * downloaded. To restrict a request to locally-cached resources, add the `only-if-cached`
 * directive:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder()
 *         .onlyIfCached()
 *         .build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * Response forceCacheResponse = client.newCall(request).execute();
 * if (forceCacheResponse.code() != 504) {
 *   // The resource was cached! Show it.
 * } else {
 *   // The resource was not cached.
 * }
 * ```
 *
 * This technique works even better in situations where a stale response is better than no response.
 * To permit stale cached responses, use the `max-stale` directive with the maximum staleness in
 * seconds:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder()
 *         .maxStale(365, TimeUnit.DAYS)
 *         .build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * ```
 *
 * The [CacheControl] class can configure request caching directives and parse response caching
 * directives. It even offers convenient constants [CacheControl.FORCE_NETWORK] and
 * [CacheControl.FORCE_CACHE] that address the use cases above.
 *
 * [rfc_7234]: http://tools.ietf.org/html/rfc7234
 */
export class Cache{
  static VERSION = 201105
  static ENTRY_METADATA = 0
  static ENTRY_BODY = 1
  static ENTRY_COUNT = 2
  static key(url: HttpUrl): string {
    let encoder = new util.TextEncoder()
    let md5 = cryptoFramework.createMd('MD5')
    md5.updateSync({data: encoder.encodeInto(url.toString())})
    let result = md5.digestSync()
    return buffer.from(result.data).toString('hex')
  }

  // @Throws(IOException::class)
  static readInt(source: FileReader): number {
    try {
      let line = source.readLine()
      let result = parseInt(line, 10);
      if (isNaN(result)) throw new Error("Not a number");
      if (result < 0 || result > Number.MAX_VALUE) {
        throw new IOException(`expected an int but was "${line}"`)
      }
      return result
    } catch (e) {
      throw new IOException(e.message)
    }
}

  private _directory: string
  private _maxSize: number
  fileSystem: FileSystem
  private cache: DiskLruCache
  // read and write statistics, all guarded by 'this'.
  _writeSuccessCount = 0
  _writeAbortCount = 0
  private _networkCount = 0
  private _hitCount = 0
  private _requestCount = 0
  _isClosed: boolean
  get isClosed() {
    this._isClosed = this.cache.isClosed()
    return this._isClosed
  }

  /** 
   * 创建缓存实例
   * @param directory 缓存目录路径（字符串）
   * @param maxSize 最大缓存大小（字节）
   * @param fileSystem 文件系统实现（可选，默认使用系统文件系统）
   */
  constructor(directory: string, maxSize: number, fileSystem?: FileSystem) {
    this._directory = directory
    this._maxSize = maxSize
    this.fileSystem = fileSystem || SystemFileSystem.SYSTEM
    this.cache  = new DiskLruCache(
      this.fileSystem,
      this._directory,
      Cache.VERSION,
      Cache.ENTRY_COUNT,
      this._maxSize,
      TaskRunner.INSTANCE
    )
  }


  get(request: Request): Response | null {
    let key = Cache.key(request.url)
    let snapshot: Snapshot | null = null
    try{
      snapshot = this.cache.get(key)
      if (snapshot === null) return null;
    }catch (e) {
      return null // Give up because the cache cannot be read.
    }
    let entry: Entry
    try{
      entry = new Entry(snapshot.getSource(Cache.ENTRY_METADATA))
    } catch (e) {
      console.log("test:: ", JSON.stringify(e))
      snapshot.close()
      return null // Give up because the cache cannot be read.
    }

    let response = entry.response(snapshot)
    if (!entry.matches(request, response)) {
      response.body?.close()
      return null
    }
    return response
  }

  put(response: Response): CacheRequest | null {
    let requestMethod = response.request.method

    if (HttpMethod.invalidatesCache(response.request.method)) {
      try {
        this.remove(response.request)
      } catch (_) {
        // The cache cannot be written.
      }
      return null
    }

    if (requestMethod != "GET") {
      // Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
      // POST requests, but the complexity of doing so is high and the benefit is low.
      return null
    }

    if (hasVaryAll(response)) {
      return null
    }

    let entry = new Entry(response)
    var editor: Editor | null = null
    try {
      editor = this.cache.edit(Cache.key(response.request.url))
      if (editor === null){
        return null
      }
      entry.writeTo(editor)
      return new RealCacheRequest(editor, this)
    } catch (_) {
      this.abortQuietly(editor)
      return null
    }
  }

  // @Throws(IOException::class)
  remove(request: Request) {
    this.cache.remove(Cache.key(request.url))
  }

  update(cached: Response, network: Response) {
    let entry = new Entry(network)
    let snapshot = (cached.body as CacheResponseBody).snapshot
    var editor: Editor | null = null
    try {
      editor = snapshot.edit()
      if (editor === null || editor === undefined){
        return // edit() returns null if snapshot is not current.
      }
      entry.writeTo(editor)
      editor.commit()
    } catch (_) {
      this.abortQuietly(editor)
    }
  }

  private abortQuietly(editor: Editor | null) {
    // Give up because the cache cannot be written.
    try {
      editor?.abort()
    } catch (_) {
    }
  }

  /**
   * Initialize the cache. This will include reading the journal files from the storage and building
   * up the necessary in-memory cache information.
   *
   * The initialization time may vary depending on the journal file size and the current actual
   * cache size. The application needs to be aware of calling this function during the
   * initialization phase and preferably in a background worker thread.
   *
   * Note that if the application chooses to not call this method to initialize the cache. By
   * default, OkHttp will perform lazy initialization upon the first usage of the cache.
   */
  // @Throws(IOException::class)
  initialize() {
    this.cache.initialize()
  }

  /**
   * Closes the cache and deletes all of its stored values. This will delete all files in the cache
   * directory including files that weren't created by the cache.
   */
  // @Throws(IOException::class)
  delete() {
    this.cache.delete()
  }

  /**
   * Deletes all values stored in the cache. In-flight writes to the cache will complete normally,
   * but the corresponding responses will not be stored.
   */
  // @Throws(IOException::class)
  evictAll() {
    this.cache.evictAll()
  }

  /**
   * Returns an iterator over the URLs in this cache. This iterator doesn't throw
   * `ConcurrentModificationException`, but if new responses are added while iterating, their URLs
   * will not be returned. If existing responses are evicted during iteration, they will be absent
   * (unless they were already returned).
   *
   * The iterator supports [MutableIterator.remove]. Removing a URL from the iterator evicts the
   * corresponding response from the cache. Use this to evict selected responses.
   */
  // @Throws(IOException::class)
  urls(): MutableIterator<string> {
    let that = this
    class UrlIterator implements MutableIterator<string>{
      private delegate: MutableIterator<Snapshot> = that.cache.snapshots()
      private nextUrl: string | null = null
      private canRemove = false

      hasNext(): boolean {
        if (this.nextUrl !== null && this.nextUrl !== undefined) return true

        this.canRemove = false // Prevent delegate.remove() on the wrong item!
        while (this.delegate.hasNext()) {
          try {
            let snapshot = this.delegate.next()
            let metadata = snapshot.getSource(Cache.ENTRY_METADATA)
            this.nextUrl = metadata.readLine()
            return true
          } catch (_) {
            // We couldn't read the metadata for this snapshot; possibly because the host filesystem
            // has disappeared! Skip it.
          }
        }
        return false
      }

      next(): string {
        if (!this.hasNext()) throw new NoSuchElementException('')
        let result = this.nextUrl!!
        this.nextUrl = null
        this.canRemove = true
        return result
      }

      remove() {
        kotlinCheck(this.canRemove, "remove() before next()")
        this.delegate.remove()
      }
    }

    return new UrlIterator()
}

  // @Synchronized fun
  writeAbortCount(): number{
    return this._writeAbortCount
  }

  // @Synchronized fun
  writeSuccessCount(): number {
    return this._writeSuccessCount
  }

  // @Throws(IOException::class)
  size(): number{
    return this.cache.size()
  }

  /** Max size of the cache (in bytes). */
  maxSize(): number{
    return this.cache.maxSize
  }

  // @Throws(IOException::class)
  flush() {
    this.cache.flush()
  }

  // @Throws(IOException::class)
  close() {
    this.cache.close()
  }

  /** 获取缓存目录路径 */
  get directory(): string {
    return this.cache.directory
  }

  trackResponse(cacheStrategy: CacheStrategy) {
    this._requestCount++

    if (cacheStrategy._networkRequest !== null && cacheStrategy._networkRequest !== undefined) {
      // If this is a conditional request, we'll increment hitCount if/when it hits.
      this._networkCount++
    } else if (cacheStrategy._cacheResponse !== null && cacheStrategy._cacheResponse !== undefined) {
      // This response uses the cache and not the network. That's a cache hit.
      this._hitCount++
    }
  }

  trackConditionalCacheHit() {
    this._hitCount++
  }

  networkCount(): number {
      return this._networkCount
  }

  hitCount(): number {
    return this._hitCount
  }

  requestCount(): number {
    return this._requestCount
  }
}

class RealCacheRequest implements CacheRequest {
  private editor: Editor
  constructor(editor: Editor, private cache: Cache) {
    this.editor = editor
    this.cacheOut = this.editor.newSink(Cache.ENTRY_BODY)
    this.init()
  }
  private cacheOut: fs.Stream
  private _body: Sink
  done = false

  init() {
    let that = this;
    class fsSink implements Sink{
      constructor(private stream: fs.Stream) {
      }
      buffer(): Sink {
        return this;
      }

      flush(): void {
        this.stream.flushSync()
      }

      write(data: string | Uint8Array, byteCount?: number | undefined): void {
        this.stream.writeSync(buffer.from(data as Uint8Array).buffer)
      }

      close(): void {
        this.stream.flushSync()
      }

    }
    class tempSink extends ForwardingSink{
      // @Throws(IOException::class)
      override async close() {
        // synchronized(this@Cache) {
        if (that.done) return
        that.done = true
        that.cache._writeSuccessCount++
      // }
      //   super.delegate.close()
        that.editor.commit()
      }
    }
    this._body = new tempSink(new fsSink(this.cacheOut))
  }

  abort() {
    // synchronized(this@Cache) {
    if (this.done) return
    this.done = true
    this.cache._writeAbortCount++
    // }
    this.cacheOut.closeSync()
    try {
      this.editor.abort()
    } catch (_) {
    }
  }

  body(): Sink {
    return this._body
  }
}

export class Entry {

  /** Synthetic response header: the local time when the request was sent. */
  private static SENT_MILLIS = "OkHttp-Sent-Millis"

  /** Synthetic response header: the local time when the response was received. */
  private static RECEIVED_MILLIS = "OkHttp-Received-Millis"

  private url: string
  private varyHeaders: Headers
  private requestMethod: string
  private protocol: Protocol
  private code: number
  private message: string
  private responseHeaders: Headers
  private handshake?: Handshake
  private sentRequestMillis: number
  private receivedResponseMillis: number

  private _isHttps: boolean
  get isHttps() {
    this._isHttps = this.url.startsWith("https://")
    return this._isHttps
  }

  /**
   * Reads an entry from an input stream. A typical entry looks like this:
   *
   * ```
   * http://google.com/foo
   * GET
   * 2
   * Accept-Language: fr-CA
   * Accept-Charset: UTF-8
   * HTTP/1.1 200 OK
   * 3
   * Content-Type: image/png
   * Content-Length: 100
   * Cache-Control: max-age=600
   * ```
   *
   * A typical HTTPS file looks like this:
   *
   * ```
   * https://google.com/foo
   * GET
   * 2
   * Accept-Language: fr-CA
   * Accept-Charset: UTF-8
   * HTTP/1.1 200 OK
   * 3
   * Content-Type: image/png
   * Content-Length: 100
   * Cache-Control: max-age=600
   *
   * AES_256_WITH_MD5
   * 2
   * base64-encoded peerCertificate[0]
   * base64-encoded peerCertificate[1]
   * -1
   * TLSv1.2
   * ```
   *
   * The file is newline separated. The first two lines are the URL and the request method. Next
   * is the number of HTTP Vary request header lines, followed by those lines.
   *
   * Next is the response status line, followed by the number of HTTP response header lines,
   * followed by those lines.
   *
   * HTTPS responses also contain SSL session information. This begins with a blank line, and then
   * a line containing the cipher suite. Next is the length of the peer certificate chain. These
   * certificates are base64-encoded and appear each on their own line. The next line contains the
   * length of the local certificate chain. These certificates are also base64-encoded and appear
   * each on their own line. A length of -1 is used to encode a null array. The last line is
   * optional. If present, it contains the TLS version.
   */
  constructor(input: Response | FileReader) {
    if (input instanceof Response){
      this.url = input.request.url.toString()
      this.varyHeaders = varyHeaders(input)
      this.requestMethod = input.request.method
      this.protocol = input.protocol
      this.code = input.code
      this.message = input.message
      this.responseHeaders = input.headers
      this.handshake = input.handshake
      this.sentRequestMillis = input.sentRequestAtMillis
      this.receivedResponseMillis = input.receivedResponseAtMillis
      return
    }
      try {
        let source = input
        this.url = source.readLine()
        this.requestMethod = source.readLine()
        let varyHeadersBuilder = new Headers.Builder()
        let varyRequestHeaderLineCount = Cache.readInt(source)
        for (let i = 0; i < varyRequestHeaderLineCount; i++) {
          varyHeadersBuilder.addLenientWithLine(source.readLine())
        }
        this.varyHeaders = varyHeadersBuilder.build()

        let statusLine = StatusLine.parse(source.readLine())
        this.protocol = statusLine.protocol
        this.code = statusLine.code
        this.message = statusLine.message
        let responseHeadersBuilder = new Headers.Builder()
        let responseHeaderLineCount = Cache.readInt(source)
        for (let i = 0; i < responseHeaderLineCount; i++) {
          responseHeadersBuilder.addLenientWithLine(source.readLine())
        }
        let sendRequestMillisString = responseHeadersBuilder.get(Entry.SENT_MILLIS)
        let receivedResponseMillisString = responseHeadersBuilder.get(Entry.RECEIVED_MILLIS)
        responseHeadersBuilder.removeAll(Entry.SENT_MILLIS)
        responseHeadersBuilder.removeAll(Entry.RECEIVED_MILLIS)
        this.sentRequestMillis = sendRequestMillisString ? parseInt(sendRequestMillisString, 10) : 0
        this.receivedResponseMillis = receivedResponseMillisString ? parseInt(receivedResponseMillisString, 10) : 0
        this.responseHeaders = responseHeadersBuilder.build()

        if (this.isHttps) {
          let blank = source.readLine()
          if (blank !== "") {
            throw new IOException(`expected "" but was "${blank}"`)
          }
          let cipherSuiteString = source.readLine()
          let cipherSuite = CipherSuite.forJavaName(cipherSuiteString)
          let peerCertificates = this.readCertificateList(source)
          let localCertificates = this.readCertificateList(source)
          let tlsVersion: TlsVersion
          if (!source.isEnd()){
            tlsVersion = TlsVersion.forJavaName(source.readLine())
          }else{
            tlsVersion = TlsVersion.SSL_3_0
          }
          this.handshake = new Handshake(tlsVersion, cipherSuiteString, localCertificates, () => peerCertificates, )
        } else {
          this.handshake = undefined
        }
      } finally {
        input.close()
      }
  }

  // @Throws(IOException::class)
  writeTo(editor: Editor) {
    let sink = editor.newSink(Cache.ENTRY_METADATA)
    sink.writeSync(buffer.from(this.url + '\n' + this.requestMethod + '\n' + this.varyHeaders.size.toString() + '\n').buffer)
    for (let i = 0; i < this.varyHeaders.size; i++) {
      sink.writeSync(buffer.from(this.varyHeaders.name(i) + ': ' + this.varyHeaders.value(i) + '\n').buffer)
    }
    sink.writeSync(buffer.from(new StatusLine(this.protocol, this.code, this.message).toString() + '\n' + (this.responseHeaders.size + 2).toString() + '\n').buffer)
    for (let i = 0; i < this.responseHeaders.size; i++) {
      sink.writeSync(buffer.from(this.responseHeaders.name(i) + ': ' + this.responseHeaders.value(i) + '\n').buffer)
    }
    sink.writeSync(buffer.from(Entry.SENT_MILLIS + ': ' + this.sentRequestMillis.toString() + '\n').buffer)
    sink.writeSync(buffer.from(Entry.RECEIVED_MILLIS + ': ' + this.receivedResponseMillis.toString() + '\n').buffer)
    if (this._isHttps && this.handshake) {
      sink.writeSync(buffer.from('\n' + this.handshake.cipherSuite + '\n').buffer)
      this.writeCertList(sink, this.handshake.peerCertificates)
      this.writeCertList(sink, this.handshake.localCertificates)
      sink.writeSync(buffer.from(this.handshake.tlsVersion.javaName + '\n').buffer)

    }
    // Flush and close the metadata sink to ensure headers are written immediately
    sink.flushSync()
    sink.closeSync()
  }

  // @Throws(IOException::class)
  private readCertificateList(source: FileReader): cert.EncodingBlob | null {
    let length = Cache.readInt(source)
    if (length == -1) return null // OkHttp v1.2 used -1 to indicate null.
    let base64Util = new util.Base64Helper()
    try {
      let result: cert.EncodingBlob | null = null
      for (let i = 0; i < length; i++) {
        let line1 = source.readLine()
        let bytes = base64Util.decodeSync(line1)
        let line2 = source.readLine()
        result = {
          data: bytes,
          encodingFormat: parseInt(line2, 10)
        }
      }
      return result
    } catch (e) {
      throw new IOException(e.message)
    }
  }

  // @Throws(IOException::class)
  private writeCertList(sink: fs.Stream, certificates: cert.EncodingBlob) {
    try {
      sink.writeSync(buffer.from('1\n').buffer)
      let bytes = certificates.data
      let line = buffer.from(bytes).toString('base64')
      sink.writeSync(buffer.from(line + '\n').buffer)
      sink.writeSync(buffer.from(certificates.encodingFormat.toString() + '\n').buffer)
    } catch (e) {
      throw new IOException(e.message)
    }
  }

  matches(request: Request, response: Response): Boolean {
    return this.url === request.url.toString() &&
      this.requestMethod === request.method &&
    varyMatches(response, this.varyHeaders, request)
  }

  response(snapshot: Snapshot): Response {
    let contentType = this.responseHeaders.get("Content-Type")
    let contentLength = this.responseHeaders.get("Content-Length")
    let cacheRequest = new Request.Builder()
      .url(this.url)
      .method(this.requestMethod, null)
      .headers(this.varyHeaders)
      .build()
    return new Response.Builder()
      .request(cacheRequest)
      .protocol(this.protocol)
      .code(this.code)
      .message(this.message)
      .headers(this.responseHeaders)
      .body(new CacheResponseBody(snapshot, contentType ?? undefined, contentLength ?? undefined))
      .handshake(this.handshake)
      .sentRequestAtMillis(this.sentRequestMillis)
      .receivedResponseAtMillis(this.receivedResponseMillis)
      .build()
  }

}

export class CacheResponseBody extends ResponseBody{
  snapshot: Snapshot
  private _contentType?: string
  private _contentLength?: string
  private bodySource: Source
  constructor(snapshot: Snapshot, contentType?: string, contentLength?: string) {
    super()
    this.snapshot = snapshot
    this._contentType = contentType
    this._contentLength = contentLength
    this.init()
  }

  init() {
    let source = this.snapshot.getSource(Cache.ENTRY_BODY)
    let that = this
    class fsSource implements Source{
      constructor(private stream: FileReader) {
      }
      _isGzip: boolean = false
      set isGzip(value: boolean) {
        this._isGzip = value
      }

      Stream(callback: (chunk: Uint8Array, complete?: boolean | undefined, error?: Error | undefined) => void): void {
        const bufferSize = 4096; // 每次读取 4KB
        const buffers = new ArrayBuffer(bufferSize);
        try {
          let len: number;
          // 2. 循环读取直到返回 0（表示 EOF）
          while ((len = this.stream.stream.readSync(buffers)) > 0) {
            // 3. 将 ArrayBuffer 转为 Uint8Array，再转为字符串
            callback(new Uint8Array(buffers).slice(0, len));
          }
        } catch (e) {
          callback(new Uint8Array(0), false, e instanceof Error ? e : new Error(String(e)));
        }finally {
          // 4. 务必关闭流
          this.stream.stream.closeSync();
        }
        callback(new Uint8Array(0), true);
      }

      async read(sink: Uint8Array, byteCount: number): Promise<number> {
        const buffers = new ArrayBuffer(byteCount);
        let len = this.stream.stream.readSync(buffers)
        sink.set(new Uint8Array(buffers, 0, len))
        return len
      }

      async readBytes(): Promise<Uint8Array> {
        const bufferSize = 4096; // 每次读取 4KB
        const buffers = new ArrayBuffer(bufferSize);
        let res: Uint8Array[] = []
        try {
          let len: number;
          // 2. 循环读取直到返回 0（表示 EOF）
          while ((len = this.stream.stream.readSync(buffers)) > 0) {
            // 3. 将 ArrayBuffer 转为 Uint8Array，再转为字符串
            res.push(new Uint8Array(buffers).slice(0, len));
          }
        } finally {
          // 4. 务必关闭流
          this.stream.stream.closeSync();
        }
        let concat = buffer.concat(res)
        return new Uint8Array(concat.buffer, 0, concat.length);
      }

      async readString(): Promise<string> {
        const bufferSize = 4096; // 每次读取 4KB
        const buffers = new ArrayBuffer(bufferSize);
        let res: Uint8Array[] = []
        try {
          let len: number;
          // 2. 循环读取直到返回 0（表示 EOF）
          while ((len = this.stream.stream.readSync(buffers)) > 0) {
            // 3. 将 ArrayBuffer 转为 Uint8Array，再转为字符串
            res.push(new Uint8Array(buffers).slice(0, len));
          }
        } finally {
          // 4. 务必关闭流
          this.stream.stream.closeSync();
        }
        return buffer.concat(res).toString();
      }

      close(): void {
        this.stream.close()
      }

      buffer(): BufferedSource {
        throw new Error('Method not implemented.')
      }

      skipAll(): void {
        throw new Error('Method not implemented.')
      }
    }
    class tempSource extends ForwardingSource {
      constructor(source: Source) {
        super(source)
      }
      override close(){
        that.snapshot.close()
        super.close()
      }
    }
    this.bodySource = new tempSource(new fsSource(source))
  }

  override contentType(): MediaType | null {
    return this._contentType ? MediaType.toMediaTypeOrNull(this._contentType) : null
  }

  override contentLength(): number {
    try {
      return this._contentLength ? parseInt(this._contentLength) : -1
    }catch (_) {
      return -1
    }
  }

  override source(): Source {
    return this.bodySource
  }
}


/**
 * Returns the names of the request headers that need to be checked for equality when caching.
 */
function varyFields(headers: Headers): TreeSet<string> {
  let result: TreeSet<string> | null = null
  for (let i = 0; i < headers.size; i++) {
    if ("Vary".toLowerCase() !== headers.name(i).toLowerCase()) {
      continue
    }

    let value = headers.value(i)
    if (result === null || result === undefined) {
      result = new TreeSet<string>((firstValue: string, secondValue: string): boolean=>{
        return firstValue.toLowerCase() < secondValue.toLowerCase()
      })
    }
    for (let varyField of value.split(',')) {
      result.add(varyField.trim())
    }
  }
  if (result === null) return new TreeSet<string>();
  return result
}

function varyHeaders(response: Response): Headers {
  // Use the request headers sent over the network, since that's what the response varies on.
  // Otherwise OkHttp-supplied headers like "Accept-Encoding: gzip" may be lost.
  let requestHeaders = response.networkResponse!!.request.headers
  let responseHeaders = response.headers
  return _varyHeaders(requestHeaders, responseHeaders)
}

/**
 * Returns the subset of the headers in [requestHeaders] that impact the content of the
 * response's body.
 */
function _varyHeaders(requestHeaders: Headers, responseHeaders: Headers): Headers {
  let tempVaryFields = varyFields(responseHeaders)
  if (tempVaryFields.isEmpty()) return EMPTY_HEADERS

  let result = new Headers.Builder()
  for (let i = 0; i < requestHeaders.size; i++) {
    let fieldName = requestHeaders.name(i)
    if (tempVaryFields.has(fieldName)) {
      result.add(fieldName, requestHeaders.value(i))
    }
  }
  return result.build()
}

/**
 * Returns true if none of the Vary headers have changed between [cachedRequest] and
 * [newRequest].
 */
function varyMatches(cachedResponse: Response, cachedRequest: Headers, newRequest: Request): boolean {
  varyFields(cachedResponse.headers).forEach((value)=>{
    if (!ArrayIsEquals<string>(cachedRequest.values(value), newRequest.headersByName(value))){
      return false
    }
  })
  return true
}

/** Returns true if a Vary header contains an asterisk. Such responses cannot be cached. */
function hasVaryAll(response: Response): boolean{
  return varyFields(response.headers).has("*")
}